// AMPEffects.x64.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include "amp.h"
#include "amp_graphics.h"
#include "shapes.h"
#include "AMPEffects.x64.h"

using namespace concurrency;
using namespace concurrency::graphics;

extern "C" __declspec ( dllexport ) void _stdcall gaussian_blur(const void * src, void * dst, 
																int image_height, int image_width,
																int radius, rectangle rois[],
																int roisLength, bool isVerticalBlur)
{
	index<2> regionOffset;
	index<2> tileExtent;
	float * weights = CreateBlurWeights(radius);
	array_view<float, 1> weightsView(radius * 2 + 1, weights);
	rectangle bounds;
	accelerator cpuAccelerator = accelerator(accelerator::cpu_accelerator);
	accelerator deviceAccelerator = accelerator(accelerator::default_accelerator);
	rectangle previousTileRect = rectangle();
	rectangle previousRect = rectangle();
	texture<uint_4, 2> image(extent<2>(1, 1));
	array<uint, 1> stagingArray(2);
	array_view<uint, 2> blurimage = stagingArray.view_as<2>(extent<2>(1,1));

	bounds.top=0; bounds.left=0; bounds.width=image_width; bounds.height=image_height;

	for (int i = 0; i < roisLength; i++)
	{
		rectangle rect = rois[i];
		rectangle tileRect = AddApron(rect, radius, bounds, isVerticalBlur);

		regionOffset[0] = rect.top - tileRect.top;
		regionOffset[1] = rect.left - tileRect.left;
		tileExtent[0] = tileRect.height - 1;
		tileExtent[1] = tileRect.width - 1;

		// Tile texture and view
		if (previousTileRect.width != tileRect.width || previousTileRect.height != tileRect.height)
		{
			image = texture<uint_4, 2> (tileRect.height, tileRect.width, 8U);
		}

		// Create tile
		void* tileSrc = CopyTile(src, tileRect, image_width, image_height);
		copy(tileSrc, tileRect.width * tileRect.height * uint_size, image);
		free(tileSrc);

		// Result buffer and view
		if (previousRect.width != rect.width || previousRect.height != rect.height)
		{
			stagingArray = array<uint, 1>(rect.width * rect.height, cpuAccelerator.default_view, deviceAccelerator.default_view);
			blurimage = stagingArray.view_as<2>(extent<2>(rect.height, rect.width));
		}
		blurimage.discard_data();

		parallel_for_each(blurimage.extent, [=, &image](index<2> idx) restrict(amp)
		{
			index<2> pixelPoint = idx + regionOffset;
			uint_4 sampleColor;
			float_4 c = 0;
			index<2> pt;
			float totalUsedWeight = 0;
			int weightIndex = 0;

			for (int i = -radius; i <= radius + 1; i++, weightIndex++)
			{
				if (isVerticalBlur)
				{
					// Vertical pass
					pt = index<2>(pixelPoint[0] + i, pixelPoint[1]);
				}
				else
				{
					// Horizontal pass
					pt = index<2>(pixelPoint[0], pixelPoint[1] + i);
				}

				if (pt[0] >= 0 && pt[1] >= 0 && pt[0] <= tileExtent[0] && pt[1] <= tileExtent[1])
				{
					sampleColor = image[pt];

					c += float_4(sampleColor) * weightsView[weightIndex];

					totalUsedWeight += weightsView[weightIndex];
				}
			}

			// Average the remaining weights
			c /= totalUsedWeight;
			
			uint_4 uc = uint_4(c);

			blurimage[idx] = uint((uc.b) + (uc.g << 8) + (uc.r << 16) + (uc.a << 24));
		});

		blurimage.synchronize();
		void * data = stagingArray.data();
		CopyStaging(dst, data, rect, image_width, image_height);

		previousTileRect = tileRect;
		previousRect = rect;
	}

	delete [] weights;
}

float * CreateBlurWeights(int radius)
{
	int length = radius * 2 + 1;
	float* kernel = new float[length];

	for (int i = 0; i <= radius; ++i)
	{
		kernel[i] = (float)(16 * (i + 1));
		kernel[length - i - 1] = kernel[i];
	}

	float div = 0.0;
	for (int i = 0; i < length; i++)
	{
		div += kernel[i];
	}

	for (int i = 0; i < length; i++)
	{
		kernel[i] /= div;
	}

	return kernel;
}

rectangle AddApron(rectangle rect, int apronRadius, rectangle maxBounds, bool isVert)
{
	if (isVert)
	{
		rect.top -= apronRadius;
		rect.height += apronRadius * 2;
	}
	else
	{
		rect.left -= apronRadius;
		rect.width += apronRadius * 2;
	}

	if (maxBounds.top > rect.top)
	{
		rect.height += rect.top -  maxBounds.top;
		rect.top = maxBounds.top;
	}
	if (maxBounds.left > rect.left)
	{
		rect.width += rect.left - maxBounds.left;
		rect.left = maxBounds.left;
	}
	if (maxBounds.left + maxBounds.width < rect.left + rect.width)
		rect.width = maxBounds.left + maxBounds.width - rect.left;
	if (maxBounds.top + maxBounds.height < rect.top + rect.height)
		rect.height = maxBounds.top + maxBounds.height - rect.top;

	return rect;
}

void * CopyTile(const void * srcImage, rectangle region, int image_width, int image_height)
{
	byte * dest;
	size_t regionSize = region.width * region.height * uint_size;
	size_t regionStride = region.width * uint_size;
	size_t srcStride = image_width * uint_size;

	// dest = new byte[region.width * region.height * uint_size];
	dest = (byte *)malloc(regionSize);

	byte * srcPtr = (byte *)srcImage;
	srcPtr += (region.top * image_width * uint_size) + (region.left * uint_size);
	byte * destPtr = dest;

	for (int i = 0; i < region.height; i++)
	{
		memcpy_s(destPtr, regionSize, srcPtr, regionStride);
		srcPtr += srcStride;
		destPtr += regionStride;
	}

	return dest;
}

void CopyStaging(void * dst, void * data, rectangle rect, int image_width, int image_height)
{
	byte * dstPtr = (byte *)dst;
	dstPtr += (rect.top * image_width * uint_size) + (rect.left * uint_size);
	byte * srcPtr = (byte *)data;
	size_t dstSize = image_width * image_height * uint_size;
	size_t dstStride = image_width * uint_size;
	size_t rectStride = rect.width * uint_size;

	for (int i = 0; i < rect.height; i++)
	{
		memcpy_s(dstPtr, dstSize, srcPtr, rectStride);
		srcPtr += rectStride;
		dstPtr += dstStride;
	}
}